/*
 * Copyright 2011 Rolf Aden
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package com.ec;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Query;

/**
 * Servlet for handling of the entity-console requests. 
 * 
 * @author Rolf Aden
 */
@SuppressWarnings("serial")
public class EntityConsoleServlet extends HttpServlet {
	
	private static final Logger LOG = Logger.getLogger(EntityConsoleServlet.class.getName());
	
	private final DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

	static final String COMMAND_PATH = "command";
	static final String COMMAND_GET_ENTITY_KINDS = "get_entity_kinds";
	static final String COMMAND_SET_LOG_LEVEL = "set_log_level";

	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		String handler = getHandler(req);
		if (handler.startsWith(COMMAND_PATH)) {
			if (checkForAjax(req, resp)) {
				handleCommand(handler.substring(COMMAND_PATH.length() + 1), req, resp);
			}
		} else {
			handleResources(handler, req, resp);
		}
	}

	public void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		String handler = getHandler(req);
		if (handler.startsWith(COMMAND_PATH)) {
			if (checkForAjax(req, resp)) {
				handleCommand(handler.substring(COMMAND_PATH.length() + 1),
						req, resp);
			}
		}
	}
	
	private void handleCommand(String handler, HttpServletRequest req,
			HttpServletResponse resp) throws IOException {
		JSONObject retValue = null;
		resp.setContentType("application/json");
		
		try {
			if (COMMAND_GET_ENTITY_KINDS.equals(handler)) {
			    JSONArray kinds = new JSONArray();
				for(String kind : getEntityKinds()) {
					kinds.put(kind);
				}
			    retValue = new JSONObject();
			    retValue.put("kinds", kinds);
			} else if (COMMAND_SET_LOG_LEVEL.equals(handler)) {
				String level = req.getParameter("level");
				Logger.getLogger("com.ec").setLevel(Level.parse(level));
				retValue = new JSONObject();
			}
		} catch (JSONException ex) {
			throw new IOException("Couldn't createcommand response", ex);
		}
		
		try {
			retValue.write(resp.getWriter());
			resp.getWriter().flush();
		} catch (JSONException ex) {
			throw new IOException("Couldn't write command response", ex);
		}
	}

	private List<String> getEntityKinds() {
		List<String> kinds = new ArrayList<String>();
		LOG.finer("getting entity kinds");
		Query query = new Query(Query.KIND_METADATA_KIND);
		for(Entity entity : ds.prepare(query).asIterable()) {
			String name = entity.getKey().getName();
			if (!name.startsWith("__")) {
    			kinds.add(entity.getKey().getName());
    			LOG.finer(entity.getKey().getName());
			}
		}
		return kinds;
	}

	private boolean checkForAjax(HttpServletRequest request,
			HttpServletResponse response) {
		if (!"XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
			LOG.log(Level.SEVERE,
					"Received unexpected non-XMLHttpRequest command. Possible CSRF attack.");
			try {
				response.sendError(HttpServletResponse.SC_FORBIDDEN,
						"Received unexpected non-XMLHttpRequest command.");
			} catch (IOException ioe) {
				throw new RuntimeException("Encountered error writing error",
						ioe);
			}
			return false;
		}
		
		return true;
	}

	private void handleResources(String handler, HttpServletRequest req,
			HttpServletResponse resp) {
		String resourceName = null;
		if (handler.equals("main")) {
			resp.setContentType("text/html");
			resourceName = "main.html";
		} else if (handler.equals("base.css")) {
			resp.setContentType("text/css");
			resourceName = "base.css";
		} else if (handler.equals("jquery.js")) {
			resp.setContentType("text/javascript");
			resourceName = "jquery-1.6.2.js";
		} else if (handler.equals("entity-console.js")) {
			resp.setContentType("text/javascript");
			resourceName = "entity-console.js";
		} else {
			try {
				resp.sendError(404);
			} catch (IOException e) {
				throw new RuntimeException("Encountered error sending 404", e);
			}
			return;
		}

		resp.setHeader("Cache-Control", "public; max-age=300");

		try {
			InputStream resourceStream = EntityConsoleServlet.class
					.getResourceAsStream("resources/" + resourceName);
			if (resourceStream == null) {
				throw new RuntimeException(
						"Couldn't find static file for the entity-console: "
								+ resourceName);
			}
			OutputStream responseStream = resp.getOutputStream();
			byte[] buffer = new byte[1024];
			int bytesRead;
			while ((bytesRead = resourceStream.read(buffer)) != -1) {
				responseStream.write(buffer, 0, bytesRead);
			}
			responseStream.flush();
		} catch (FileNotFoundException e) {
			throw new RuntimeException(
					"Couldn't find resource for entity-console", e);
		} catch (IOException e) {
			throw new RuntimeException(
					"Couldn't read resource for entity-console", e);
		}

	}

	private static String getHandler(HttpServletRequest request) {
		String requestURI = request.getRequestURI();
		return requestURI.substring(getDividingIndex(requestURI) + 1);
	}

	private static int getDividingIndex(String fullPath) {
		int baseEnd = fullPath.lastIndexOf("/");
		if (fullPath.substring(0, baseEnd).endsWith(COMMAND_PATH)) {
			baseEnd = fullPath.substring(0, baseEnd).lastIndexOf("/");
		}
		return baseEnd;
	}
}
